Skip to content

test(verify): autonomous capture-mode toggle verifier - #167

Merged
stozo04 merged 2 commits into
mainfrom
feature/photo-mode-verifier
Sep 1, 2026
Merged

test(verify): autonomous capture-mode toggle verifier#167
stozo04 merged 2 commits into
mainfrom
feature/photo-mode-verifier

Conversation

@stozo04

@stozo04 stozo04 commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Description

Adds the autonomous installed-APK verifier for the capture-mode toggle — the top-right
CAMERA | VIDEO selector on the viewfinder — and writes "mark INVENTORY when it passes" into the
create-verifier process, which until now happened by hand.

No shipped app code changes: verifier plus skill docs only.

The verifier

verify-openloop/helpers/photo_mode_loop.py drives the real APK on an emulator through
Video → Camera → Video, and asserts each state out of a single uiautomator dump, two ways at
once:

State Shutter contentDescription Camera segment Video segment
video-baseline Start recording checked=false checked=true
camera-mode Take photo checked=true checked=false
video-restored Start recording checked=false checked=true

The shutter label is the ability being switched — whether the next tap records a clip or takes
a picture. The checked flag is the indicator — which segment carries the lime fill, and the
reason the single toggling icon became a segmented control at all (issue #126: with one icon, the
only state indicator sat under the fingertip at the exact moment it changed). Half a flip fails: a
highlight that moves while the shutter still records, or a shutter that flips while the pill stays
put.

Both reads come off one dump on purpose. A dump costs seconds on this screen, so polling for the
label and then re-dumping for the flag is a race the harness loses intermittently — and the two
halves disagreeing is exactly the bug being looked for.

Nothing is captured: no still, no clip, no gallery write, no DataStore reset. The loop runs in
about 25 s.

Where selection state actually lives

verify_common.py nodes now carry checkable / checked (in both parsers). Finding the flag
meant reading the real hierarchy rather than guessing at it: Compose puts the selectable on a
full-height wrapper around the visible pill, and only that wrapper reports checked. The label
leaf and the android.widget.RadioButton leaf inside it both report checked="false" whichever
segment is selected — so a verifier that reached for the obvious-looking RadioButton node would
have asserted a constant and passed forever.

uiautomator returns a flat node list, so the loop finds the wrapper geometrically: the smallest
checkable node whose bounds contain the label's. On the shipped screen that resolves to exactly two
nodes, [523,175][786,301] and [786,175][1027,301].

Evidence, and proof the assertions discriminate

Run on emulator-5584 (Pixel_8, API 34):

  • PASS serial=emulator-5584 selector=Camera|Video video->photo->video took=26s from .claude,
    and 21 s from the synced .cursor copy — which is the one run-verification-loops.py globs.
  • Re-parsing the three saved XMLs afterwards, each state matches its own mode and fails the
    other
    . A verifier that passes while asserting nothing is the failure mode worth ruling out
    explicitly, not assuming away.
  • Both parsers (etree and the regex fallback) agree node-for-node on a real dump, so the fallback
    path carries the new fields too.
  • Evidence per run: video-baseline, camera-mode, video-restored as XML + PNG.

Sweep gate 5b picked the loop up with no registry change, since run-verification-loops.py globs
*_loop.py: PASS loops=onboarding,photo_mode,record_clip.

INVENTORY, and making that a step

INVENTORY.md gains a row for the capture-mode toggle as automated 2026-09-01. "Photo stills
mode" deliberately stays mapped
— this loop drives the toggle, not the capture, and the table's
own legend makes automated a claim about what a loop drives. Overstating it there is how a green
table starts lying.

Marking that row was ad-hoc (#163 did it by hand as well), so create-verifier/SKILL.md now names
it as step 6 — only after the loop has been seen passing — with the harness sync moved to the end
so it picks up the INVENTORY edit too.

Manual QA / not verified here

  • Taking the actual still is still a manual control.ps1 recipe; this loop stops at the toggle.
  • The toggle is hidden while recording and mid-booth (CameraScreen), and setCaptureMode refuses
    a switch mid-recording. Neither guard is driven here — that is record-clip / photo-booth
    territory.
  • One emulator (Pixel_8, API 34). Not run on a Fold or a physical device.

Checklist

  • 🧪 My changes have been verified locally and work as expected.

  • 🔍 I have performed a self-review of my own code.

  • ✍️ I have commented my code, particularly in hard-to-understand areas.

  • 📖 My changes generate no compile warnings or errors (allWarningsAsErrors is on).

  • 🧹 Pre-PR sweep GREEN on the final commit (.\scripts\pre-pr-sweep.ps1
    build/sweep-receipt.json), run as -SkipInspectCode -SkipConnected. Two gates are therefore
    NOT in the receipt, and both need the owner before merge:

    • Inspect Code (Engine 2): SKIPPED — needs the IDE run with the "OpenLoop Tracked" scope,
      which is not available headless on this machine.
    • Instrumented tests + the verification loops (gates 5 / 5b): SKIPPED-SkipConnected
      skips both. This is a host limit, not a code one: this machine cannot hold a full sweep and the
      AVD at once (the Gradle daemon reaches ~3 GB and does not give it back), and the owner chose
      this over freeing memory. What was actually observed at this same commit, across three full
      sweep attempts, is below.
    Attempt Gate 5b (loops) Gate 5 (instrumented)
    1 PASS loops=onboarding,photo_mode,record_clip red before running — a phantom adb device appeared, ANDROID_SERIAL unset
    2 PASS loops=onboarding,photo_mode,record_clip 121 tests, 1 failure
    3 PASS loops=onboarding,photo_mode,record_clip died at 78 tests

    Neither gate-5 red is an assertion failing. Attempt 2's was
    BoomerangEditorScreenTest#selectedLook_reportsSelectedSemantics timing out at exactly 45 s in
    ActivityScenarioRule.after waiting for DESTROYED — teardown, not the one-line test body — with
    2.6 GB free; stopping the Gradle daemons took the host to 6.0 GB and that class then ran 25/25
    green in isolation
    . Attempt 3 died with DeadSystemRuntimeException: DeadSystemException, the
    emulator's own system_server going down with 1.1 GB free. connectedDebugAndroidTest still
    needs one clean run on a machine with headroom before merge.

  • 🏪 Play-facing docs aligned: no permission, data-collection, storage or user-facing change
    — verifier and repo tooling only.

  • 🧹 The git branch is clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01249no1TrwKLR1aorGqaf7C


Note

Low Risk
Verifier tooling and documentation only; no production app, auth, or data-handling changes.

Overview
Adds an installed-APK verification loop for the viewfinder Camera | Video segmented control (photo_mode_loop.py), synced across the three harness skill trees. The loop drives Video → Camera → Video on an emulator without capturing media, and from each uiautomator dump asserts both the shutter accessibility label (Start recording vs Take photo) and the selected segment’s checked state so a half-updated toggle fails.

Harness plumbing: verify_common.UiNode and both XML parsers now expose checkable / checked, with geometry-based lookup of the Compose wrapper that actually reports selection. Docs/process: photo-capture.md documents the autonomous run command; INVENTORY.md marks the capture-mode toggle automated 2026-09-01 while photo still capture stays mapped. create-verifier workflow moves harness sync to a new step 6 (after a passing run and INVENTORY update) instead of immediately after updating the feature recipe.

No changes to shipped Android app code.

Reviewed by Cursor Bugbot for commit e551a20. Bugbot is set up for automated code reviews on this repo. Configure here.

stozo04 and others added 2 commits September 1, 2026 10:04
Adds photo_mode_loop.py, the verifier for the `photo-mode` sub-feature of
features/photo-capture.md: the top-right CAMERA | VIDEO selector and what a tap
on it actually does. It drives Video -> Camera -> Video on the installed debug
APK and asserts each state out of a SINGLE uiautomator dump, two ways at once:

  * the shutter's contentDescription (`Start recording` <-> `Take photo`) — the
    ability being switched, and
  * the tapped segment's `checked` flag — the only indicator of which mode is
    armed, which is why the single toggling icon became a segmented control
    (issue #126).

Half a flip fails: a highlight that moves while the shutter still records, or a
shutter that flips while the pill stays put. Re-dumping between the two reads
would be a race, so both come off one dump. Nothing is captured — no still, no
clip, no gallery write.

verify_common gains `checkable`/`checked` on UiNode (both parsers). Compose puts
`selectable` on a full-height wrapper around the visible pill, and only that
wrapper reports `checked` — the label leaf and the RadioButton-class leaf both
read checked="false" whichever segment is selected — so the loop finds the
wrapper by bounds containment.

No registry change needed: run-verification-loops.py globs `*_loop.py`, so sweep
gate 5b picks this up automatically.

Verified on emulator-5584 (Pixel_8, API 34): PASS in 26s from .claude and 21s
from the synced .cursor copy, with video-baseline / camera-mode / video-restored
XML + PNG evidence. Re-parsing that evidence confirms each state matches its own
mode and fails the other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01249no1TrwKLR1aorGqaf7C
…hat a step

INVENTORY gains a row for the capture-mode toggle (`automated 2026-09-01`) — the
surface photo_mode_loop.py actually drives. "Photo stills mode" deliberately
stays `mapped`: taking the still is still a manual control.ps1 recipe, and the
table's own legend makes `automated` a claim about what a loop drives.

Marking that row was ad-hoc; the create-verifier skill now names it as step 6,
after the loop has been seen passing, with the harness sync moved to the end so
it picks up the INVENTORY edit too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01249no1TrwKLR1aorGqaf7C
@stozo04
stozo04 merged commit 0a13b97 into main Sep 1, 2026
3 checks passed
@stozo04
stozo04 deleted the feature/photo-mode-verifier branch September 1, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant